package net.wicp.tams.common.tracer;

import java.math.BigInteger;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import com.google.common.base.Optional;

import lombok.extern.slf4j.Slf4j;

/**
 * Start a new trace. Start a new span within a trace. Attach to a span within a
 * trace.
 */
@Slf4j
public abstract class Span implements AutoCloseable, SpanData {
	private final BigInteger traceId;
	private final Optional<BigInteger> parentSpanId;
	private final BigInteger spanId;
	private final String name;
	private final String host;
	private final Integer pid;
	private final TraceLevel traceLevel;
	private final long startTime;
	protected final List<AnnotationData> annotations;
	private final UUID guid;
	private long duration;

	protected Span(Optional<BigInteger> parentSpanId, BigInteger spanId, String name, BigInteger traceId,
			long startTime, long startNanos, TraceLevel traceLevel) {
		this.guid = UUID.randomUUID();
		this.host = Annotations.getServiceAnnotations().getHost();
		this.pid = Annotations.getServiceAnnotations().getPid();
		this.parentSpanId = parentSpanId;
		this.spanId = spanId;
		this.name = name;
		this.traceId = traceId;
		this.startTime = startTime;
		this.duration = startNanos;
		this.traceLevel = traceLevel;
		this.annotations = new LinkedList<>();

	}

	public abstract void addAnnotation(String name);

	public abstract void addAnnotation(String name, String message);

	protected abstract void afterClose();

	public final void end() {
		duration = System.nanoTime() - duration;
		Optional<SpanContext> context = SpanHelper.currentContext();
		if (context.isPresent()) {
			context.get().endSpan(this);
			afterClose();
		} else {
			throw new IllegalStateException("Span.end() from a detached span.");
		}
	}

	@Override
	public final void close() {
		end();
	}

	public final BigInteger getTraceId() {
		return traceId;
	}

	public final BigInteger getSpanId() {
		return spanId;
	}

	public final Optional<BigInteger> getParentSpanId() {
		return parentSpanId;
	}

	public final String getName() {
		return name;
	}

	public final String getHost() {
		return host;
	}

	@SuppressWarnings("UnusedDeclaration")
	public final Integer getPid() {
		return pid;
	}

	@Override
	public final long getStartTime() {
		return startTime;
	}

	@Override
	public final long getDuration() {
		return duration;
	}

	@Override
	public final List<AnnotationData> getAnnotations() {
		return annotations;
	}

	final TraceLevel getTraceLevel() {
		return traceLevel;
	}

	@Override
	public String toString() {
		return "Span{" + "traceId=" + traceId + ", parentSpanId=" + parentSpanId + ", spanId=" + spanId + ", name='"
				+ name + '\'' + ", startTime=" + startTime + ", duration=" + duration + '}';
	}

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;

		Span span = (Span) o;

		return guid.equals(span.guid);
	}

	@Override
	public int hashCode() {
		return guid.hashCode();
	}
}
